package com.tbit.uqbike.service.base.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.tbit.common.constant.ParkPointConstant;
import com.tbit.common.entity.pojo.vo.ReturnBikeMsgLogVO;
import com.tbit.common.entity.view.Result;
import com.tbit.uqbike.constant.*;
import com.tbit.uqbike.constant.enums.ParkPointLevelEnum;
import com.tbit.uqbike.enums.ParkPointType;
import com.tbit.uqbike.enums.SystemCodeEnum;
import com.tbit.uqbike.manager.exception.BaseException;
import com.tbit.uqbike.object.business.OrderInfo;
import com.tbit.uqbike.object.business.Point;
import com.tbit.uqbike.object.event.ParkPointDeleteEvent;
import com.tbit.uqbike.object.pojo.*;
import com.tbit.uqbike.object.pojo.dto.GetReturnBikePointDTO;
import com.tbit.uqbike.object.pojo.dto.ParkPointMachineStaDownloadDTO;
import com.tbit.uqbike.object.pojo.statistics.ExportParkPoint;
import com.tbit.uqbike.object.pojo.vo.ParkPointMachineStatChartVO;
import com.tbit.uqbike.object.value.LoginInfo;
import com.tbit.uqbike.service.base.CalibrateService;
import com.tbit.uqbike.service.base.ParkPointService;
import com.tbit.uqbike.service.business.AccountConfigExtService;
import com.tbit.uqbike.service.business.TerControlService;
import com.tbit.uqbike.service.rpcservice.PurviewService;
import com.tbit.uqbike.webmanager.dao.core.AccountConfigDao;
import com.tbit.uqbike.webmanager.dao.core.ParkPointDao;
import com.tbit.uqbike.webmanager.dao.log.ReturnBikeMsgLogDao;
import com.tbit.uqbike.webmanager.dao.stat.ParkPointMachineStatDao;
import com.tbit.uqbike.webmanager.util.Assert;
import com.tbit.uqbike.webmanager.util.MapUtils;
import com.tbit.uqbike.webmanager.util.StringUtil;
import com.tbit.uqbike.webmanager.util.csv.CsvExportUtils;
import com.tbit.uqbike.webmanager.util.excel.ExcelUtil;
import com.tbit.utils.GeoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.tbit.utils.DateTimeUtil.generateDateHourList;

/**
 * @author chenmengran
 * @Description: 停车点
 * @date 2019\12\12 001214:01
 */
@Slf4j
@Service
public class ParkPointServiceImpl implements ParkPointService {

    @Autowired
    private PurviewService purviewService;
    @Autowired
    private ParkPointDao parkPointDao;
    @Autowired
    private AccountConfigDao accountConfigDao;
    @Resource
    private CalibrateService calibrateService;
    @Resource
    private ParkPointMachineStatDao parkPointMachineStatDao;
    @Resource
    private CsvExportUtils csvExportUtils;

    @Autowired
    private ReturnBikeMsgLogDao returnBikeMsgLogDao;

    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private TerControlService terControlService;
    @Autowired
    private AccountConfigExtService accountConfigExtService;

    private static void checkCenter(ParkPoint parkPoint) {
        if (parkPoint.getParkPointType() == 1) {
            //多边形电子围栏
            /*List<Coordinate> points = GeoUtil.getCoordinate(parkPoint.getPoints());
            List<Coordinate> pointsC = GeoUtil.getCoordinate(parkPoint.getPointsC());
            Point newPoint = GeoUtil.getCenterOfGravityPoint4(points);
            Point newPointC = GeoUtil.getCenterOfGravityPoint4(pointsC);*/
            List<Point> points = GeoUtil.getPoints(parkPoint.getPoints());
            List<Point> pointsC = GeoUtil.getPoints(parkPoint.getPointsC());
            Point newPoint = GeoUtil.GetCenterPoint(points);
            Point newPointC = GeoUtil.GetCenterPoint(pointsC);
            parkPoint.setLaC(newPointC.getY());
            parkPoint.setLoC(newPointC.getX());
            parkPoint.setLo(newPoint.getX());
            parkPoint.setLa(newPoint.getY());
        }
    }

    @Override
    public ParkPoint insert(ParkPoint parkPoint, LoginInfo loginInfo) {
        //权限验证
        if (!purviewService.purviewCheck(loginInfo.id, parkPoint.getAccountId())) {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        checkCenter(parkPoint);
        //判断站点名字不能重复
        checkParkPointName(parkPoint.getAccountId(), parkPoint.getName());
        checkHighPrecisionAllowRange(parkPoint.getHighPrecisionAllowRange());
        parkPointDao.insert(parkPoint);
        return parkPoint;
    }

    @Override
    public ParkPoint update(ParkPoint parkPoint, LoginInfo loginInfo) {
        //权限验证
        if (!purviewService.purviewCheck(loginInfo.id, parkPoint.getAccountId())) {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        checkCenter(parkPoint);
        //判断站点名字不能重复
        ParkPoint parkPointInDb = parkPointDao.getByParkPointId(parkPoint.getParkPointId());
        if (!parkPointInDb.getName().equals(parkPoint.getName())) {
            //如果名字有更新不一样，需要判断是否重复
            checkParkPointName(parkPointInDb.getAccountId(), parkPoint.getName());
        }


        if (ParkPointType.PARKPOINT_POLYGON_TYPE.getType().equals(String.valueOf(parkPoint.getParkPointType()))) {
            if (isHighPrecisionSite(parkPoint)) {
                //高精度站点不改坐标,保留原始经纬度
                parkPoint.setPoints(null);
                parkPoint.setPointsC(null);
            }
        }
        checkHighPrecisionAllowRange(parkPoint.getHighPrecisionAllowRange());
        parkPointDao.update(parkPoint);
        return parkPoint;
    }

    /**
     * 判断高精度站点
     *
     * @param parkPoint
     * @return
     */
    private boolean isHighPrecisionSite(ParkPoint parkPoint) {
        return SiteTypeConstant.HIGH_PRECISION_SITE.equals(parkPoint.getSiteType());
    }

    /**
     * 判断站点名字是否重复
     *
     * @param accountId 区域id
     * @param name      站点名称
     */
    private void checkParkPointName(Integer accountId, String name) {
        Integer count = parkPointDao.checkParkPointName(accountId, name);
        if (count > 0) {
            throw new BaseException("该运营区内有重复的站点名称");
        }
    }

    @Override
    public void del(Integer parkPointId, LoginInfo loginInfo) {
        ParkPoint parkPoint = parkPointDao.getByParkPointId(parkPointId);
        if (parkPoint == null) {
            throw new BaseException(SystemCodeEnum.PARKPOINT_NOT_EXIT);
        }
        //权限验证
        if (!purviewService.purviewCheck(loginInfo.id, parkPoint.getAccountId())) {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        List<String> userCodes = parkPointDao.getByParkPointIds(parkPointId);
        parkPointDao.del(parkPoint.getParkPointId());
        for (String userCode : userCodes) {
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setUserCode(userCode);
            orderInfo.setParamName(ControlTypeConstant.CONTROL_TYPE_LOCATION);
            orderInfo.setControlType(ControlTypeConstant.CONTROL_TYPE_CONTROL);
            orderInfo.setDesc("删除站点触发设备立即定位");
            orderInfo.setBusinessCode(null);
            terControlService.sendControl(loginInfo,orderInfo);
        }

        //删除站点车辆绑定关系
        CompletableFuture.runAsync(() -> publishParkPointDeleteEvent(Arrays.asList(parkPointId)));
    }

    /**
     * 发布站点删除绑定关系事件
     * @param parkPointIds 站点id
     */
    private void publishParkPointDeleteEvent(List<Integer> parkPointIds) {
        if (CollectionUtil.isNotEmpty(parkPointIds)) {
            ParkPointDeleteEvent event = new ParkPointDeleteEvent(this, parkPointIds);
            eventPublisher.publishEvent(event);
        }
    }

    @Override
    public List<ParkPoint> getByAccountId(Integer accountId, LoginInfo loginInfo, List<Integer> regionIdList, Integer level) {
        //权限验证
        if (!purviewService.purviewCheck(loginInfo.id, accountId)) {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("regionIdList", regionIdList);
        params.put("level", level);
        return parkPointDao.getByRegionId(params);
    }

    @Override
    public Boolean checkInParkPoint(Integer accountId, Point point, Integer radius) {
        Boolean inParkPoint = false;

        List<ParkPoint> parkPoints = getByAccountAndArea(accountId, point.getX(), point.getY(), 150);

        /** 判断车辆是否在站点要求的经纬度内 */
        for (ParkPoint parkPoint : parkPoints) {
            if (parkPoint.getAllowRange() == -1) {
                List<AccountConfigExt> accountConfigExtList = accountConfigExtService.selectAndAddAccountConfigExtList(parkPoint.getAccountId(), ConfigConstant.ALL_PARK_POINT_ALLOW_RANGE);
                for (AccountConfigExt accountConfigExt : accountConfigExtList) {
                    if (Objects.equals(accountConfigExt.getParamKey(), ConfigConstant.ALL_PARK_POINT_ALLOW_RANGE)) {
                        parkPoint.setAllowRange(Integer.valueOf(accountConfigExt.getParamValue()));
                    }
                }
                parkPoint.setAllowRange(Optional.ofNullable(parkPoint.getAllowRange()).orElse(20));
            }
            Double distince;
            Integer pointType = parkPoint.getParkPointType();
            if (pointType != null && pointType.equals(1)) {
                /** 停车点是多边形 */
                List<Point> points = GeoUtil.getPoints(parkPoint.getPoints());
                /**判断是否在多边形内*/
                if (GeoUtil.IsPtInPoly(point, points)) {
                    return true;
                }

                distince = com.tbit.uqbike.webmanager.util.GeoUtil.getDistance(point, points);
            } else {
                /** 默认停车点是圆形 */
                distince = MapUtils.distance(point.getX(), point.getY(), parkPoint.getLo(), parkPoint.getLa());
            }

            /** 圆形误差范围最小限制100米，多边形误差范围最小限制50米 */
            Integer allowRange = 50;
            Integer parkAllowRange = parkPoint.getAllowRange();
            if (pointType != null && pointType.equals(1)) {
                allowRange = parkAllowRange < 100 ? 100 : parkAllowRange;
            } else {
                allowRange = parkAllowRange < 50 ? 50 : parkAllowRange;
            }

            if (distince <= allowRange && parkPoint.getAccountId().equals(accountId)) {
                return true;
            }
        }

        return inParkPoint;
    }

    @Override
    public List<ParkPoint> getByAccountAndArea(Integer accountId, Double lo, Double la, Integer radius) {
        AccountConfig accountConfig = accountConfigDao.getById(accountId);
        if (accountConfig != null && accountConfig.getBigParkPoint() == 1) {
            return parkPointDao.getByAccountId(accountId);
        } else {
            Double area = 0.00001D * radius;
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("accountId", accountId);
            params.put("minLo", lo - area);
            params.put("maxLo", lo + area);
            params.put("minLa", la - area);
            params.put("maxLa", la + area);
            return parkPointDao.getByAccountAndArea(params);
        }
    }

    @Override
    public ParkPoint getByParkPointId(Integer parkPointId) {
        return parkPointDao.getByParkPointId(parkPointId);
    }

    @Override
    public PageInfo<ParkPoint> queryPage(LoginInfo loginInfo, Integer accountId, String machineNO, Integer rowCount,
                                         Integer pageNO, String sortItem, String name, List<Integer> regionIdList,
                                         Integer enable, String rfid, Integer siteType, Integer rfidType, Integer railSpikeType,
                                         Integer kickstandType, Integer cameraType,Integer level) {
        //权限验证
        if (!purviewService.purviewCheck(loginInfo.id, accountId)) {
            throw new BaseException(SystemCodeEnum.NO_PRIVILEGE);
        }
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("machineNO", machineNO);
        if (name != null && !"" .equals(name)) {
            params.put("name", "%" + name + "%");
        }
        params.put("start", (pageNO - 1) * rowCount);
        params.put("rowCount", rowCount);
        params.put("regionIdList", regionIdList);
        params.put("enable", enable);
        params.put("rfid", rfid);
        params.put("siteType", siteType);

        //查出符合的站点id
        params.put("rfidType", rfidType);
        params.put("railSpikeType", railSpikeType);
        params.put("kickstandType", kickstandType);
        params.put("cameraType", cameraType);
        params.put("level", level);
        //List<ParkPoint> parkPoints = parkPointDao.queryPage(params);
        PageInfo<ParkPoint> pageInfo = PageHelper.startPage(pageNO, rowCount).doSelectPageInfo(() -> parkPointDao.queryPage(params));
        return pageInfo;
    }

    @Override
    public Integer getTotal(Integer accountId, String name, String machineNO, List<Integer> regionIdList, Integer enable, String rfid, Integer siteType, Integer rfidType, Integer railSpikeType, Integer kickstandType, Integer cameraType) {
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("machineNO", machineNO);
        if (name != null && !"" .equals(name)) {
            params.put("name", "%" + name + "%");
        }
        params.put("regionIdList", regionIdList);
        params.put("enable", enable);
        params.put("rfid", rfid);
        params.put("siteType", siteType);
        params.put("rfidType", rfidType);
        params.put("railSpikeType", railSpikeType);
        params.put("kickstandType", kickstandType);
        params.put("cameraType", cameraType);
        List<Integer> ids = parkPointDao.queryParkPoint(params);

        params.put("idList", ids);
        return parkPointDao.getTotal(params);
    }

    @Override
    public List<ParkPoint> queryByName(LoginInfo loginInfo, Integer accountId, String name) {
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountId);
        params.put("name", "%" + name + "%");
        return parkPointDao.queryByName(params);
    }

    @Override
    public List<ParkPoint> getAll() {
        return parkPointDao.getAll();
    }

    @Override
    public List<HeatMapStat> statByPark(Integer accountId, Integer type, Long startTime,
                                        Long endTime, String regionIds) {
        Map<String, Object> map = new HashMap<>();
        map.put("accountId", accountId);
        map.put("startTime", new Date(startTime));
        map.put("endTime", new Date(endTime));
        map.put("regionIds", StringUtil.getIntegerList(regionIds));
        if (type == 0) {
            return parkPointDao.statByStartPark(map);
        } else {
            return parkPointDao.statByEndPark(map);
        }
    }

    @Override
    public List<ExportParkPoint> selectParkPoint(Map<String, Object> map) {
        return parkPointDao.selectParkPoint(map);
    }

    @Override
    public void switchParkPoint(Integer pSwitch, Integer parkPointId) {
        Map<String, Object> map = new HashMap<>();
        map.put("enable", pSwitch);
        map.put("parkPointId", parkPointId);
        Integer result = parkPointDao.updateParkPoint(map);
        if (result == 0) {
            throw new BaseException(SystemCodeEnum.OPERATIOBMN_FAILED);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAdd(MultipartFile file, Integer accountId, LoginInfo loginInfo, Integer mapType) {

        List<BatchAddParkPoint> addParkPoints = ExcelUtil.readAll(file, BatchAddParkPoint.class);

        //校验站点名称是否已存在
        Set<String> siteNameSet = addParkPoints.stream().map(BatchAddParkPoint::getSiteName).collect(Collectors.toSet());
        for (List<String> list : CollUtil.split(siteNameSet, 1000)) {
            if (parkPointDao.checkParkPointNameBySet(accountId, list) > 0){
                throw new BaseException("站点名称重复");
            }
        }
        if (addParkPoints.size() != siteNameSet.size()) {
            throw new BaseException("存在相同的站点名称");
        }

        /*批量插入对象*/
        List<ParkPoint> parkPointList = new ArrayList<>();

        for (BatchAddParkPoint batchAddParkPoint : addParkPoints) {
            Assert.notNull(batchAddParkPoint.getSiteName(), "站点名称不能为空");
            Assert.notNull(batchAddParkPoint.getCapacity(), "站点容量不能为空");
            Assert.notNull(batchAddParkPoint.getPoints(), "经纬度不能为空");
            if (Objects.isNull(batchAddParkPoint.getLevel())) {
                batchAddParkPoint.setLevel("普通站点");
            }

            checkPoints(batchAddParkPoint.getPoints());
            /*校验高精度误差范围(单位厘米)*/
            checkHighPrecisionAllowRange(batchAddParkPoint.getHighPrecisionAllowRange());

            // 根据经纬度小数点后有没有大于等于6位来判断是否是高精度站点
            String pointStr = batchAddParkPoint.getPoints();
            Integer siteType = getSiteType(pointStr);

            // 对高精度站点导入不做校验 校验多边形面积是否小于最小值
            if (!UserConstant.USER_ADMIN.equals(loginInfo.type) && siteType.equals(SiteTypeConstant.GENERAL_SITE) && GeoUtil.pointArea(GeoUtil.getPoints(pointStr)) < 10) {
                throw new BaseException("parkPoint.point.minArea");
            }

            ParkPoint parkPoint = new ParkPoint();
            parkPoint.setAccountId(accountId);
            parkPoint.setSiteType(siteType);
            parkPoint.setName(batchAddParkPoint.getSiteName());
            parkPoint.setAllowRange(batchAddParkPoint.getAllowRange());
            parkPoint.setHighPrecisionAllowRange(batchAddParkPoint.getHighPrecisionAllowRange());
            parkPoint.setParkPointType(Integer.valueOf(ParkPointType.PARKPOINT_POLYGON_TYPE.getType()));
            parkPoint.setCapacity(batchAddParkPoint.getCapacity());
            parkPoint.setPoints(batchAddParkPoint.getPoints());
            parkPoint.setLevel(ParkPointLevelEnum.getByLevelName(batchAddParkPoint.getLevel()));
            if (MapConstant.MAP_ORI.equals(mapType)) {
                calibrateService.calParkPoint(parkPoint, MapConstant.MAP_ORI, MapConstant.MAP_GOOGLE);
            } else {
                calibrateService.calParkPoint(parkPoint, MapConstant.MAP_GOOGLE, MapConstant.MAP_ORI);
            }

            /*计算中心经纬度*/
            checkCenter(parkPoint);

            parkPointList.add(parkPoint);
        }

        /*批量新增站点*/
        for (List<ParkPoint> parkPoints : CollUtil.split(parkPointList, 1000)) {
            parkPointDao.insertBatch(parkPoints);
        }


    }

    @Override
    public Page<ParkPointMachineStat> selectParkPointMachineStatPage(Integer accountId, String startTime, String endTime,
                                                                     String regionIds, String parkPointIds,
                                                                     String orderBy,Integer rowCount, Integer pageNO) {
        LambdaQueryWrapper<ParkPointMachineStat> lambdaQueryWrapper = buildParkPointMachineStatQueryWrapper(accountId, startTime, endTime, regionIds, parkPointIds);
        lambdaQueryWrapper.orderBy(true, "ASC".equalsIgnoreCase(orderBy), ParkPointMachineStat::getId);
        return parkPointMachineStatDao.selectPage(new Page<>(pageNO, rowCount), lambdaQueryWrapper);
    }

    private LambdaQueryWrapper<ParkPointMachineStat> buildParkPointMachineStatQueryWrapper(Integer accountId, String startTime,
                                                                                           String endTime, String regionIds,
                                                                                           String parkPointIds) {
        String startDataDate = DateUtil.format(DateUtil.parse(startTime), "yyyy-MM-dd");
        String endDataDate = DateUtil.format(DateUtil.parse(endTime), "yyyy-MM-dd");
        int startTimeSlot = DateUtil.parse(startTime, "yyyy-MM-dd HH").getField(Calendar.HOUR_OF_DAY);
        int endTimeSlot = DateUtil.parse(endTime, "yyyy-MM-dd HH").getField(Calendar.HOUR_OF_DAY);
        LambdaQueryWrapper<ParkPointMachineStat> lambdaQueryWrapper = Wrappers.lambdaQuery(ParkPointMachineStat.class)
                .eq(ParkPointMachineStat::getAccountId, accountId)
                .between(StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime), ParkPointMachineStat::getDataDate, startDataDate, endDataDate)
                .between(ParkPointMachineStat::getTimeSlot, startTimeSlot, endTimeSlot);
        if (StringUtils.isNoneBlank(regionIds, parkPointIds) || (StringUtils.isBlank(regionIds) && StringUtils.isNotBlank(parkPointIds))) {
            lambdaQueryWrapper.in(ParkPointMachineStat::getParkPointId, CollUtil.newHashSet(parkPointIds.split(",")));
        } else if (StringUtils.isNotBlank(regionIds) && StringUtils.isBlank(parkPointIds)) {
            LambdaQueryWrapper<ParkPoint> parkPointLambdaQueryWrapper = Wrappers.lambdaQuery(ParkPoint.class)
                    .select(ParkPoint::getParkPointId)
                    .in(ParkPoint::getRegionId, CollUtil.newHashSet(StringUtils.split(regionIds, ",")));
            Set<Integer> parkPointIdSet = parkPointDao.selectList(parkPointLambdaQueryWrapper)
                    .stream()
                    .map(ParkPoint::getParkPointId)
                    .collect(Collectors.toSet());
            lambdaQueryWrapper.in(ParkPointMachineStat::getParkPointId, parkPointIdSet);
        }
        return lambdaQueryWrapper;
    }

    private List<ParkPoint> getParkPointsByConditions(String regionIds, String parkPointIds, Integer accountId) {
        if (StringUtils.isNoneBlank(regionIds, parkPointIds) || (StringUtils.isBlank(regionIds) && StringUtils.isNotBlank(parkPointIds))) {
            LambdaQueryWrapper<ParkPoint> parkPointLambdaQueryWrapper = Wrappers.lambdaQuery(ParkPoint.class)
                    .select(ParkPoint::getParkPointId, ParkPoint::getName)
                    .in(ParkPoint::getParkPointId, CollUtil.newHashSet(StringUtils.split(parkPointIds, ",")));
            return parkPointDao.selectList(parkPointLambdaQueryWrapper);
        } else if (StringUtils.isNotBlank(regionIds) && StringUtils.isBlank(parkPointIds)) {
            LambdaQueryWrapper<ParkPoint> parkPointLambdaQueryWrapper = Wrappers.lambdaQuery(ParkPoint.class)
                    .select(ParkPoint::getParkPointId, ParkPoint::getName)
                    .in(ParkPoint::getRegionId, CollUtil.newHashSet(StringUtils.split(regionIds, ",")));
            return parkPointDao.selectList(parkPointLambdaQueryWrapper);
        } else {
            return parkPointDao.getByAccountId(accountId);
        }
    }

    @Override
    public String parkPointMachineStatExportCsv(ParkPointMachineStaDownloadDTO parkPointMachineStaDownloadDTO) {
        File tempFile = csvExportUtils.getTempFile();
        LambdaQueryWrapper<ParkPointMachineStat> lambdaQueryWrapper = buildParkPointMachineStatQueryWrapper(
                parkPointMachineStaDownloadDTO.getAccountId(),
                DateUtil.formatDateTime(parkPointMachineStaDownloadDTO.getStartTime()),
                DateUtil.formatDateTime(parkPointMachineStaDownloadDTO.getEndTime()),
                parkPointMachineStaDownloadDTO.getRegionIds(),
                parkPointMachineStaDownloadDTO.getParkPointIds()
        );
        lambdaQueryWrapper.orderBy(true, "ASC".equalsIgnoreCase(parkPointMachineStaDownloadDTO.getOrderBy()), ParkPointMachineStat::getAddTime);
        List<ParkPointMachineStat> dataResult = parkPointMachineStatDao.selectList(lambdaQueryWrapper);
        try {
            File file = csvExportUtils.exportCsv(ParkPointMachineStat.class, tempFile,
                    parkPointMachineStaDownloadDTO.getFileName(), dataResult, false);
            String uploadFileName = UUID.randomUUID().toString().replace("-", "");
            //压缩文件并上传oss
            return csvExportUtils.uploadZip(tempFile, uploadFileName, file);
        } finally {
            //删除临时文件
            FileUtil.del(tempFile);
        }
    }

    @Override
    public List<ParkPointMachineStatChartVO> selectParkPointMachineStatChart(Integer accountId, String startTime, String endTime,
                                                                             String regionIds, String parkPointIds) {
        List<String> dateHourList = generateDateHourList(startTime, endTime, true);

        List<ParkPoint> parkPoints = getParkPointsByConditions(regionIds, parkPointIds, accountId);

        LambdaQueryWrapper<ParkPointMachineStat> lambdaQueryWrapper = buildParkPointMachineStatQueryWrapper(accountId, startTime, endTime, regionIds, parkPointIds);
        Map<String, Integer> availableRentCountMap = parkPointMachineStatDao.selectList(lambdaQueryWrapper)
                .stream()
                .collect(Collectors.toMap(stat -> stat.getDataDate() + "_" + stat.getTimeSlot() + "_" + stat.getParkPointId(),
                        stat -> Optional.ofNullable(stat.getAvailableRentCount()).orElse(0)));

        List<ParkPointMachineStatChartVO> resultList = new ArrayList<>();
        for (ParkPoint parkPoint : parkPoints) {
            ParkPointMachineStatChartVO chartVO = new ParkPointMachineStatChartVO(parkPoint.getParkPointId(), parkPoint.getName());
            for (String hourKey : dateHourList) {
                String groupKey = hourKey + "_" + parkPoint.getParkPointId();
                Integer availableRentCount = availableRentCountMap.getOrDefault(groupKey, 0);
                String[] dateSlot = hourKey.split("_");
                chartVO.addData(new ParkPointMachineStatChartVO.ParkPointMachineStatVO(dateSlot[0], Integer.valueOf(dateSlot[1]), availableRentCount));
            }
            resultList.add(chartVO);
        }
        return resultList;
    }

    /**
     * 站点明细导出
     * @param parkPointMachineStaDownloadDTO 导出参数
     * @return 文件名称
     */
    @Override
    public Result<Object> parkPointMachineStatExport(ParkPointMachineStaDownloadDTO parkPointMachineStaDownloadDTO) {
        LambdaQueryWrapper<ParkPointMachineStat> lambdaQueryWrapper = buildParkPointMachineStatQueryWrapper(
                parkPointMachineStaDownloadDTO.getAccountId(),
                DateUtil.formatDateTime(parkPointMachineStaDownloadDTO.getStartTime()),
                DateUtil.formatDateTime(parkPointMachineStaDownloadDTO.getEndTime()),
                parkPointMachineStaDownloadDTO.getRegionIds(),
                parkPointMachineStaDownloadDTO.getParkPointIds()
        );
        lambdaQueryWrapper.orderBy(true, "ASC".equalsIgnoreCase(parkPointMachineStaDownloadDTO.getOrderBy()), ParkPointMachineStat::getAddTime);
        List<ParkPointMachineStat> dataResult = parkPointMachineStatDao.selectList(lambdaQueryWrapper);
        String fileName = String.format("%s~%s_站点运营数据统计", DateUtil.formatDate(parkPointMachineStaDownloadDTO.getStartTime()), DateUtil.formatDate(parkPointMachineStaDownloadDTO.getEndTime()));
        ExcelUtil<ParkPointMachineStat> excelUtil = new ExcelUtil(ParkPointMachineStat.class);
        return Result.success(excelUtil.exportExcel(dataResult, fileName).getData());
    }

    @Override
    public void checkHighPrecisionAllowRange(Integer allowRange) {
        if (allowRange != null && allowRange > 10000) {
            throw new BaseException("高精度站点的最大误差范围是10000cm");
        }
    }

    @Override
    public List<ReturnBikeMsgLogVO> getReturnBikePoint(GetReturnBikePointDTO dto) {
        if (dto.getDistance() == null) {
            dto.setDistance(200);
        }
        dto.compute(calibrateService);
        List<ReturnBikeMsgLogVO> returnBikePoint = returnBikeMsgLogDao.getReturnBikePoint(dto);
        for (ReturnBikeMsgLogVO returnBikeMsgLogVo : returnBikePoint) {
            if (returnBikeMsgLogVo.getLon() != null && returnBikeMsgLogVo.getLat() != null) {
                Point point = calibrateService.calibrateOffSet(returnBikeMsgLogVo.getLon(), returnBikeMsgLogVo.getLat(), MapConstant.MAP_GOOGLE);
                returnBikeMsgLogVo.setLonC(point.getX());
                returnBikeMsgLogVo.setLatC(point.getY());
            }
        }
        return returnBikePoint;
    }

    /**
     * 获取站点类型
     * 高精度站点 经纬度小数位的长度大于6
     * 普通站点  经纬度小数位的长度小于等于6
     *
     * @param pointsStr
     * @return
     */
    @Override
    public Integer getSiteType(String pointsStr) {
        List<Point> points = GeoUtil.getPoints(pointsStr);
        for (Point point : points) {
            int xDecimalLength = StrUtil.subAfter(point.getX().toString(), ".", true).length();
            int yDecimalLength = StrUtil.subAfter(point.getY().toString(), ".", true).length();
            if (xDecimalLength > 6 || yDecimalLength > 6) {
                return SiteTypeConstant.HIGH_PRECISION_SITE;
            }
        }
        return SiteTypeConstant.GENERAL_SITE;
    }

    @Override
    public void checkPoints(String points) {
        try {
            List<Point> pointsList = GeoUtil.getPoints(points);
            if (pointsList.size() < 3) {
                throw new BaseException("经纬度不合法");
            }
            for (Point point : pointsList) {
                if (!GeoUtil.checkLoLa(point)) {
                    throw new BaseException("经纬度不合法");
                }
            }
        } catch (Exception e) {
            throw new BaseException("经纬度不合法");
        }
    }

    @Override
    public Boolean isMaxParkPointCount(Integer accountId) {
        return parkPointDao.checkCountByAccountId(accountId, ParkPointConstant.LARGE_MODE_MAX_PARK_POINT_COUNT);
    }

    @Override
    public List<ParkPoint> selectByAccountId(Integer accountId) {
        return parkPointDao.selectByAccountId(accountId);
    }

    @Override
    public void batchDel(List<Integer> parkPointList) {
        parkPointDao.deleteBatchIds(parkPointList);
    }
}
